TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { notFound } from "next/navigation";3import { ArrowLeft } from "lucide-react";4import { requireAdmin } from "@/lib/session";5import { CONCRETE_NETWORKS, PROVIDER_IDS, getDomainProfile } from "@/lib/queries/admin";6import { formatDate, formatMs, formatNumber, formatPercent, formatUsd } from "@/lib/format";7import { PageHeader } from "@/components/ui/page-header";8import { Badge, StatusBadge } from "@/components/ui/badge";9import { Button } from "@/components/ui/button";10import { Stat, StatGrid } from "@/components/ui/stat";11import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";12import { DateCell, KV, Mono, NetworkBadge, Panel, ProviderBadge, numCell } from "@/components/admin/primitives";13import { DomainPolicyEditor, ResetDomainStatsButton } from "@/components/admin/domain-policy-editor";1415export const dynamic = "force-dynamic";1617export default async function AdminDomainDetail({ params }: { params: Promise<{ domain: string }> }) {18 await requireAdmin();19 const { domain: raw } = await params;20 const domain = decodeURIComponent(raw).toLowerCase();21 const d = await getDomainProfile(domain);22 if (!d) notFound();23 const p = d.profile;24 const routes = Object.entries(p.routeStats ?? {}).sort((a, b) => b[1].n - a[1].n);25 const availableKeys = Array.from(new Set([...routes.map(([k]) => k), ...PROVIDER_IDS.flatMap((pr) => CONCRETE_NETWORKS.map((n) => `${pr}:${n}`))]));2627 return (28 <>29 <div className="mb-4">30 <Button asChild variant="ghost" size="sm" className="-ml-2">31 <Link href="/admin/domains">32 <ArrowLeft className="size-3.5" /> Domains33 </Link>34 </Button>35 </div>36 <PageHeader37 eyebrow="Domain profile"38 title={<Mono className="text-[20px]">{p.domain}</Mono>}39 description={`Learned since ${formatDate(p.updatedAt)} · last seen ${formatDate(p.lastSeenAt)}`}40 actions={41 <>42 <Button asChild variant="outline" size="sm">43 <Link href={`/admin/requests?domain=${encodeURIComponent(p.domain)}`}>Explore requests</Link>44 </Button>45 <ResetDomainStatsButton domain={p.domain} />46 </>47 }48 />4950 <StatGrid cols={6} className="mb-6">51 <Stat label="Requests" value={formatNumber(p.requests)} />52 <Stat label="Success" value={formatPercent(p.requests ? (p.successes / p.requests) * 100 : null)} />53 <Stat label="Blocked" value={formatPercent(p.requests ? (p.blocks / p.requests) * 100 : null)} hint={`${p.blocks} blocks`} />54 <Stat label="Captcha" value={formatPercent(p.requests ? (p.captchas / p.requests) * 100 : null)} hint={`${p.captchas} seen`} />55 <Stat label="Browser required" value={formatNumber(p.browserRequired)} hint="signals seen" />56 <Stat label="Avg latency" value={formatMs(p.avgLatencyMs)} />57 </StatGrid>5859 <div className="grid gap-6 lg:grid-cols-2">60 <Panel title="Route statistics" description="Per provider:network key, as used by the routing score (35 % success, 15 % latency)." flush>61 <Table>62 <TableHeader>63 <TableRow>64 <TableHead>Route</TableHead>65 <TableHead className="text-right">n</TableHead>66 <TableHead className="text-right">ok</TableHead>67 <TableHead className="text-right">Success</TableHead>68 <TableHead className="text-right">Blocked</TableHead>69 <TableHead className="text-right">Avg lat.</TableHead>70 <TableHead className="text-right">Cost</TableHead>71 <TableHead className="text-right">Cost / success</TableHead>72 </TableRow>73 </TableHeader>74 <TableBody>75 {routes.length === 0 ? (76 <TableEmpty colSpan={8}>No route statistics yet.</TableEmpty>77 ) : (78 routes.map(([key, s]) => {79 const [prov, net] = key.split(":");80 return (81 <TableRow key={key}>82 <TableCell>83 <span className="inline-flex items-center gap-1.5">84 <ProviderBadge id={prov} />85 <NetworkBadge network={net} />86 </span>87 </TableCell>88 <TableCell className={numCell}>{s.n}</TableCell>89 <TableCell className={numCell}>{s.ok}</TableCell>90 <TableCell className={numCell}>{formatPercent(s.n ? (s.ok / s.n) * 100 : null)}</TableCell>91 <TableCell className={numCell}>{formatPercent(s.n ? (s.blocked / s.n) * 100 : null)}</TableCell>92 <TableCell className={numCell}>{formatMs(s.lat)}</TableCell>93 <TableCell className={numCell}>{formatUsd(s.cost, true)}</TableCell>94 <TableCell className={`${numCell} font-semibold`}>{formatUsd(s.ok ? s.cost / s.ok : null, true)}</TableCell>95 </TableRow>96 );97 })98 )}99 </TableBody>100 </Table>101 </Panel>102 <div className="grid gap-6">103 <Panel title="Policy" description="Admin overrides applied before scoring. Saved immediately; takes effect on the next request.">104 <DomainPolicyEditor domain={p.domain} policy={p.policy ?? null} availableKeys={availableKeys} />105 </Panel>106 <Panel title="Learned preference">107 <KV108 cols={2}109 items={[110 { label: "Preferred provider", value: <ProviderBadge id={p.preferredProvider} /> },111 { label: "Preferred network", value: <NetworkBadge network={p.preferredNetwork} /> },112 { label: "Policy JSON", value: p.policy ? JSON.stringify(p.policy) : "none", mono: true, span: true },113 ]}114 />115 </Panel>116 </div>117 </div>118119 <Panel title="Recent requests (7d)" flush className="mt-6">120 <Table>121 <TableHeader>122 <TableRow>123 <TableHead>Request</TableHead>124 <TableHead>Organization</TableHead>125 <TableHead>Status</TableHead>126 <TableHead>Network</TableHead>127 <TableHead className="text-right">Attempts</TableHead>128 <TableHead className="text-right">Latency</TableHead>129 <TableHead className="text-right">When</TableHead>130 </TableRow>131 </TableHeader>132 <TableBody>133 {d.recent.length === 0 ? (134 <TableEmpty colSpan={7}>No requests in the last 7 days.</TableEmpty>135 ) : (136 d.recent.map((r) => (137 <TableRow key={r.id}>138 <TableCell>139 <Link href={`/admin/requests/${r.id}`} className="hover:underline">140 <Mono>{r.id}</Mono>141 </Link>142 </TableCell>143 <TableCell>{r.orgName ?? "—"}</TableCell>144 <TableCell>145 <StatusBadge status={r.status} />146 {r.errorCode ? <Badge variant="outline" className="ml-1 font-mono">{r.errorCode}</Badge> : null}147 {r.httpStatus ? <Mono className="ml-1 text-fg-muted">{r.httpStatus}</Mono> : null}148 </TableCell>149 <TableCell>150 <NetworkBadge network={r.network} />151 </TableCell>152 <TableCell className={numCell}>{r.attempts}</TableCell>153 <TableCell className={numCell}>{formatMs(r.latencyMs)}</TableCell>154 <TableCell className="text-right">155 <DateCell value={r.createdAt} />156 </TableCell>157 </TableRow>158 ))159 )}160 </TableBody>161 </Table>162 </Panel>163 </>164 );165}166